package com.anlogic.sdk.linker.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;

import com.anlogic.sdk.linker.LScript;
import com.anlogic.sdk.linker.MemRangeSection;
import com.anlogic.sdk.linker.Memory;

public class LinkerViewer extends FormPage {
	private FormToolkit toolkit;

	private ScrolledForm form;

	private TableViewer memoryTableViewer;

	private Text stackSizeText;

//	private Text heapSizeText;

//	private String heapSizeTextValue = "";

	private TableViewer memRangeSectionTableViewer;

	private ComboBoxCellEditor sectionMemoryMapEditor;

	private MemRangeSectionCellModifier memRangeSectionCellModifier;

	private LinkerEditor linkerEditor;

	private LScript lscript;

	private List<MemRangeSection> memRangeSections;

	private Map<String, String> memRegionRenameMap = new HashMap<String, String>();

	public LinkerViewer(FormEditor editor, String id, String title) {
		super(editor, id, title);
		this.linkerEditor = (LinkerEditor) editor;
	}

	@Override
	protected void createFormContent(IManagedForm managedForm) {
		this.toolkit = managedForm.getToolkit();
		this.form = managedForm.getForm();
		this.form.getBody().setLayout(new TableWrapLayout());
		this.form.setText("Linker Script: " + this.getEditor().getEditorInput().getName());
		this.toolkit.decorateFormHeading(this.form.getForm());
		addDesciption(this.form.getBody());
		addMemoryRegion(this.form.getBody());
		addStackHeapSize(this.form.getBody());
		addMemoryMapping(this.form.getBody());
	}

	private Composite createGridComposite(Section section, int cols, boolean makeColumnsEqualWidth) {
		Composite c = this.toolkit.createComposite(section);
		GridLayout l = new GridLayout(cols, makeColumnsEqualWidth);
		c.setLayout((Layout) l);
		section.setClient((Control) c);
		return c;
	}

	private Composite createComposite(Section section, int cols) {
		Composite c = this.toolkit.createComposite((Composite) section);
		TableWrapLayout l = new TableWrapLayout();
		l.numColumns = cols;
		c.setLayout((Layout) l);
		section.setClient((Control) c);
		return c;
	}

	private Section createSection(Composite parent, String title) {
		Section s = this.toolkit.createSection(parent, SWT.FOREGROUND);
		toolkit.createCompositeSeparator((Composite) s);
		s.setText(title);
		TableWrapData td = new TableWrapData();
		td.colspan = 1;
		td.grabHorizontal = true;
		s.setLayoutData(td);
		return s;
	}

	private void addDesciption(Composite parent) {
		this.toolkit.createLabel(parent,
				"A linker script is used to control where different sections of an executable are placed in memory.\nIn this page, you can define new memory regions, and change the assignment of sectionsto memory regions.\n");
	}

	private void addMemoryRegion(Composite parent) {
		Section section = createSection(parent, "Available Memory Regions");

		Composite c = createComposite(section, 2);

		Table table = toolkit.createTable(c,
				SWT.BORDER | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE);
		this.memoryTableViewer = new TableViewer(table);

		int[] memoryTableBounds = { 350, 150, 150 };

		for (int i = 0; i < Memory.PROPERTIES.length; i++) {
			TableViewerColumn column = new TableViewerColumn(this.memoryTableViewer, SWT.FILL);
			column.getColumn().setText(Memory.PROPERTIES[i]);
			column.getColumn().setWidth(memoryTableBounds[i]);
			column.getColumn().setResizable(true);
			column.getColumn().setMoveable(false);
		}

		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		table.setLayout((Layout) new GridLayout(1, false));
		table.setLayoutData(new TableWrapData());

		CellEditor[] editors = new CellEditor[3];
		editors[0] = (CellEditor) new TextCellEditor((Composite) table);
		editors[1] = (CellEditor) new TextCellEditor((Composite) table);
		editors[2] = (CellEditor) new TextCellEditor((Composite) table);

		this.memoryTableViewer.setCellEditors(editors);
		this.memoryTableViewer.setCellModifier(new MemoryCellModifier(this));
		this.memoryTableViewer.setColumnProperties(Memory.PROPERTIES);
		this.memoryTableViewer.setLabelProvider((IBaseLabelProvider) new MemoryRangeLabelProvider());
		this.memoryTableViewer.setContentProvider((IContentProvider) new MemoryRangeContentProvider());
		this.memoryTableViewer.setInput(this.lscript.getMemoryRanges());
		this.memoryTableViewer.refresh();

		Composite buttonComp = toolkit.createComposite(c);
		buttonComp.setLayout((Layout) new GridLayout(1, false));
		buttonComp.setLayoutData(new TableWrapData());

		Button addButton = toolkit.createButton(buttonComp, "Add", SWT.NONE);
		addButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
		addButton.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				addNewMemoryRegion();
				modelChanged();
				refreshView();
			}
		});

		Button delButton = toolkit.createButton(buttonComp, "Delete", SWT.NONE);
		delButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
		delButton.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				TableItem[] items = table.getSelection();
				if (items != null && items.length > 0) {
					deleteMemoryRegion(items[0].getText());
					modelChanged();
					refreshView();
				}
			}
		});
	}

	public void refreshModel() {
		refreshMemories(this.lscript.getMemoryRanges());
		refreshMemRangeSections(lscript.getMemRangeSections());
	}

	private void refreshMemRangeSections(Object input) {
		if (this.memRangeSectionTableViewer != null) {
			this.memRangeSectionTableViewer.setInput(input);
			this.memRangeSectionTableViewer.refresh();
		}
	}

	private void refreshMemories(Object input) {
		if (this.memoryTableViewer != null) {
			this.memoryTableViewer.setInput(input);
			this.memoryTableViewer.refresh();
		}
	}

	public void setStackSize(String s) {
		if (this.stackSizeText != null)
			this.stackSizeText.setText(s);
	}

//	public void setHeapSize(String s) {
//		if (this.heapSizeText != null)
//			this.heapSizeText.setText(s);
//	}

	private void addStackHeapSize(Composite parent) {
		Section section = createSection(form.getBody(), "Stack and Heap Sizes");
		Composite c = createGridComposite(section, 2, false);

		GridData gd = new GridData();
		gd.widthHint = 80;

		toolkit.createLabel(c, "Stack Size");
		this.stackSizeText = new Text(c, SWT.BORDER);
		this.stackSizeText.setText(this.lscript.getStackSize());
		this.stackSizeText.setLayoutData(gd);

//		toolkit.createLabel(c, "Heap Size");
//		this.heapSizeText = new Text(c, SWT.BORDER);
//		this.heapSizeText.setText(this.heapSizeTextValue);
//		this.heapSizeText.setLayoutData(gd);

		this.stackSizeText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				lscript.setStackSize(stackSizeText.getText());
				modelChanged();
				refreshView();
			}
		});

//		this.heapSizeText.addModifyListener(new ModifyListener() {
//			public void modifyText(ModifyEvent e) {
//				lscript.setHeapSize(heapSizeText.getText());
//				modelChanged();
//				refreshView();
//			}
//		});
	}

	private void addMemoryMapping(Composite parent) {
		Section section = createSection(parent, "Section to Memory Region Mapping");
		Composite c = createComposite(section, 2);

		Table table = toolkit.createTable(c,
				SWT.BORDER | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE);

		this.memRangeSectionTableViewer = new TableViewer(table);

		int[] memoryMapBounds = { 150, 350 };

		for (int i = 0; i < MemRangeSection.PROPERTIES.length; i++) {
			TableViewerColumn column = new TableViewerColumn(this.memRangeSectionTableViewer, 4);
			column.getColumn().setText(MemRangeSection.PROPERTIES[i]);
			column.getColumn().setWidth(memoryMapBounds[i]);
			column.getColumn().setResizable(true);
			column.getColumn().setMoveable(false);
		}

		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		table.setLayout((Layout) new GridLayout(1, false));
		table.setLayoutData(new TableWrapData());

		CellEditor[] editors = new CellEditor[2];
		editors[0] = (CellEditor) new TextCellEditor((Composite) table);
		this.sectionMemoryMapEditor = new ComboBoxCellEditor((Composite) table,
				getMemoryNames().toArray(new String[0]));
		editors[1] = (CellEditor) this.sectionMemoryMapEditor;

		this.memRangeSectionTableViewer.setCellEditors(editors);
		this.memRangeSectionCellModifier = new MemRangeSectionCellModifier(this, getMemoryNames());
		this.memRangeSectionTableViewer.setCellModifier(this.memRangeSectionCellModifier);
		this.memRangeSectionTableViewer.setColumnProperties(MemRangeSection.PROPERTIES);
		this.memRangeSectionTableViewer.setLabelProvider((IBaseLabelProvider) new MemRangeSectionLabelProvider());
		this.memRangeSectionTableViewer.setContentProvider((IContentProvider) new MemRangeSectionContentProvider());
		this.memRangeSectionTableViewer.setInput(this.lscript.getMemRangeSections());
		this.memRangeSectionTableViewer.refresh();

		Composite buttonComp = toolkit.createComposite(c);
		buttonComp.setLayout((Layout) new GridLayout(1, false));
		buttonComp.setLayoutData(new TableWrapData());

		Button addButton = toolkit.createButton(buttonComp, "Add", SWT.NONE);
		addButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
		addButton.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				addSection();
				modelChanged();
				refreshView();
			}
		});

		Button delButton = toolkit.createButton(buttonComp, "Delete", SWT.NONE);
		delButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
		delButton.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				TableItem[] items = table.getSelection();
				if (items != null && items.length > 0) {
					deleteSection(items[0].getText());
					modelChanged();
					refreshView();
				}
			}
		});
	}

	private List<String> getMemoryNames() {
		List<Memory> mRanges = this.lscript.getMemoryRanges();
		List<String> memories = new ArrayList<String>();
		for (Memory mem : mRanges) {
			memories.add(mem.getRealName());
		}
		return memories;
	}

	private void addNewMemoryRegion() {
		String name = "newMemory" + this.lscript.getMemoryRanges().size();
		String base = "0x0";
		String size = "0x0";
		this.lscript.addMemory(new Memory(name, base, size));
	}

	private void deleteMemoryRegion(String name) {
		this.lscript.deleteMemory(name);
	}

	private void addSection() {
		String name = "newSection" + this.lscript.getMemRangeSections().size();
		String memRange = getMemoryNames().get(0);
		this.lscript.addMemRangeSection(new MemRangeSection(name, memRange));
	}

	private void deleteSection(String name) {
		this.lscript.deleteSection(name);
	}

	public void modelChanged() {
		List<String> memNames = getMemoryNames();
		this.memRangeSections = this.lscript.getMemRangeSections();
		for (MemRangeSection section : this.memRangeSections) {
			if (!memNames.contains(section.getMemoryRange())) {
				String newName = getNewName(section.getMemoryRange());
				if (newName != null) {
					section.setMemoryRange(newName);
					continue;
				}
				section.setMemoryRange(memNames.get(0));
			}
		}
		for (MemRangeSection section : this.memRangeSections) {
			if (!memNames.contains(section.getPhyMemory())) {
				String newName = getNewName(section.getPhyMemory());
				if (newName != null) {
					section.setPhyMemory(newName);
					continue;
				}
				section.setPhyMemory(memNames.get(0));
			}
		}
		this.memoryTableViewer.refresh();
		this.sectionMemoryMapEditor.setItems(getMemoryNames().<String>toArray(new String[0]));
		this.memRangeSectionCellModifier.setMemories(getMemoryNames());
		this.memRangeSectionTableViewer.refresh();
		this.linkerEditor.fireDirty();
		this.memRegionRenameMap.clear();
	}

	public void memoryRenamed(String orig, String newValue) {
		this.memRegionRenameMap.put(orig, newValue);
	}

	private String getNewName(String orig) {
		if (this.memRegionRenameMap.containsKey(orig))
			return this.memRegionRenameMap.get(orig);
		return null;
	}

	public void refreshView() {
		this.refreshModel();
		if (!this.form.isDisposed()) {
			if (this.form.getBody() != null)
				this.form.getBody().layout();
			if (this.form.getParent() != null)
				this.form.getParent().layout();
		}
	}

	public void setModel(LScript script, LinkerEditor editor) {
		if (this.form != null && this.form.isDisposed()) {
			return;
		}
		this.lscript = script;
		this.linkerEditor = editor;
		this.refreshMemories(script.getMemoryRanges());
		this.refreshMemRangeSections(script.getMemRangeSections());
		if (script.getStackSize() != null) {
			this.setStackSize(script.getStackSize());
			// this.setHeapSize(script.getHeapSize());
		}
	}
}
